package com.zs.oauth2.oauth2authorizationserver.config;

import com.zs.oauth2.common.core.constant.SecurityConstants;
import com.zs.oauth2.common.service.security.Oauth2ClientDetailsService;
import com.zs.oauth2.common.service.security.Oauth2User;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author madison
 */
@Configuration
@RequiredArgsConstructor
@EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    private final RedisConnectionFactory redisConnectionFactory;

    private final AuthenticationManager authenticationManager;

    private final UserDetailsService userDetailsService;

    private final DataSource dataSource;

    //    private TokenStore tokenStore = new InMemoryTokenStore();
    private final String NOOP_PASSWORD_ENCODE = "{noop}";

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // https://www.yuque.com/pig4cloud/pig/dqnyuc
        // 这个类就要求了密码必须符合带上{"具体的解密器id"}，最后根据这个id去找密码匹配器匹配，
        // clientSecret最终也是要参与解码的，所以它也需要带上{"id"},clientSecret我们并不需要做什么艰深的加密，
        // 所以使用原始密码就行，这个解密器就是NoOpPasswordEncoder，它的id从上文我们看到是"noop",
        // 也就是说数据库里的clientSecret要想在Spring Security5下正常工作，
        // clientId应该是testclientSecret应该是{noop}test,
        // 但是我们可以看到数据库里存储的都是test/test那为什么进行解密的时候没有抛出PasswordEncoder mapped for the id “null”的异常呢？
        // persist clients details
        Oauth2ClientDetailsService clientDetailsService = new Oauth2ClientDetailsService(dataSource);
        // 自定义SQL
        // clientDetailsService.setSelectClientDetailsSql("");
        clients.withClientDetails(clientDetailsService);

        // inMemory
//        clients.inMemory()
//
//                .withClient("zs1")
//                .secret(NOOP_PASSWORD_ENCODE + "123456").authorizedGrantTypes("password", "refresh_token").scopes("server", "test")
//
//                .and()
//                .withClient("zs2")
//                .accessTokenValiditySeconds(3600)
//                .refreshTokenValiditySeconds(604800)
//                .redirectUris("http://baidu.com")
//                .secret(NOOP_PASSWORD_ENCODE + "123456").authorizedGrantTypes("authorization_code", "refresh_token").scopes("server")
//
//                .and()
//                .withClient("zs3")
//                .secret(NOOP_PASSWORD_ENCODE + "123456").authorizedGrantTypes("client_credentials").scopes("server")
//
//                .and()
//                .withClient("zs4")
//                .redirectUris("http://baidu.com")
//                .secret(NOOP_PASSWORD_ENCODE + "123456").authorizedGrantTypes("implicit").scopes("server");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        //获取令牌不需要认证，校验令牌需要认证，允许表单认证
        // 必须加，不然资源服务无权 获取资源
        oauthServer
//                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
        // /oauth/token 默认支持POST，开启支持GET
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                //开始password模式 和UserDetailsService 一起使用
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .tokenServices(tokenService())
                //自定义
//                .pathMapping("/oauth/error", "/token/error")
                .pathMapping("/oauth/confirm_access", "/token/confirm_access");
    }


    @Bean
    public TokenStore tokenStore() {
        RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
        tokenStore.setPrefix("zs_oauth:access:");
        return tokenStore;
    }

    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {
            final Map<String, Object> additionalInfo = new HashMap<>(1);
            //修改token 增强逻辑，如果为客户端模式就不进行增强，即不维护用户信息
            if ("client_credentials"
                    .equals(authentication.getOAuth2Request().getGrantType())) {
                return accessToken;
            }
            Oauth2User oauth2User = (Oauth2User) authentication.getUserAuthentication().getPrincipal();
            additionalInfo.put(SecurityConstants.DETAILS_LICENSE, SecurityConstants.PROJECT_LICENSE);
            additionalInfo.put(SecurityConstants.DETAILS_USER_ID, oauth2User.getMyId());
            additionalInfo.put(SecurityConstants.DETAILS_USERNAME, oauth2User.getUsername());
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            return accessToken;
        };
    }

    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
//		defaultTokenServices.setClientDetailsService(clientDetailsService);
        defaultTokenServices.setTokenStore(tokenStore());  //关联存储方式
        defaultTokenServices.setTokenEnhancer(tokenEnhancer());
//        defaultTokenServices.setSupportRefreshToken(true);
//        defaultTokenServices.setAccessTokenValiditySeconds(120); //令牌有效期
//        defaultTokenServices.setRefreshTokenValiditySeconds(259200); //刷新令牌有效期
        return defaultTokenServices;
    }

}
